import 'dart:async';

import 'package:family_expense/features/account_user/models/account_group.dart';
import 'package:family_expense/features/account_user/services/account_group_service.dart';
import 'package:family_expense/features/bank_card/models/bank_card.dart';
import 'package:family_expense/features/bank_card/services/bank_card_service.dart';
import 'package:family_expense/features/income_expense/constants/income_expense_type.dart';
import 'package:family_expense/features/income_expense/constants/payment_type.dart';
import 'package:family_expense/features/income_expense_detail_type/models/income_expense_detail_type.dart';
import 'package:family_expense/features/income_expense_detail_type/services/income_expense_detail_type_loader.dart';
import 'package:flutter/material.dart';
import 'package:my_flutter_baidu_map/my_flutter_baidu_map.dart';
import 'package:my_flutter_core/my_flutter_core.dart';

/// For system data initialization.
/// Loading these data needs the user authentication.
/// DON'T do this before login.
class DataInitialization {
  final String _url = '/data-initialization';

  /// All keys for necessary init items.
  final String allDataInitializedKey = 'allDataInitialized';

  final HttpRequestService _httpRequestService = HttpRequestService();

  Timer? _timer;

  /// Stores all init item status.
  late Map<String, bool> _initStatus;

  /// Initializes all necessary data.
  ///
  /// Why not cache the data on local storage?
  /// There are concerns.
  /// - When the other users updated these data then the cache on local storage will NOT be updated until next re-login.
  /// - When the app is re-installed for update the cache on local storage would remain unchanged until next re-login.
  Future<void> init(VoidCallback? onComplete, {Function? onError}) async {
    // Baidu Map needs to init ASAP.
    await MapLocationService().init();

    // Loads last coordinates from local storage.
    await MapService.loadLastCoordinates();

    // Stores all default values. When each item initialized the value will be updated.
    _initStatus = {allDataInitializedKey: false};

    // Starts every initialization.
    _retrieveAll()
        .then((res) {
          _initStatus[allDataInitializedKey] = true;
        })
        .catchError((err) {
          _onError(err, onError);
        });

    // Starts timer to check if all initializations have completed.
    Timer.periodic(const Duration(milliseconds: 1), (timer) {
      _timer ??= timer;

      // When all initializations have completed we need to cancel [_timer] and call [onComplete] callback.
      if (_initStatus.values.where((p) => p == false).toList().isEmpty) {
        _timer!.cancel();

        onComplete?.call();
      }
    });
  }

  /// Clears all cache data.
  void clear() {
    AccountGroupService().clearCache();
    BankCardService().clearCache();
    IncomeExpenseDetailTypeLoader.clearCache();
    IncomeExpenseType.clearCache();
    PaymentType.clearCache();
  }

  /// Any error must cancel [_timer].
  void _onError(Error error, Function? onError) {
    if (_timer?.isActive == true) {
      _timer?.cancel();
    }

    onError?.call(error);
  }

  /// Retrieves all data for initialization.
  Future<void> _retrieveAll() async {
    // Gets the response of the http get request.
    final response = await _httpRequestService.get('$_url/retrieve-all');

    final result = response.payload as Map<String, dynamic>;

    AccountGroupService().init(
      loadedAccountGroup: AccountGroup.fromJson(result['accountGroupInfo']),
    );

    BankCardService().init(
      loadedBankCards: (result['bankCards'] as List<dynamic>)
          .map((p) => BankCard.fromJson(p))
          .toList(),
    );

    IncomeExpenseDetailTypeLoader.init(
      loadedDetailTypes: (result['detailTypes'] as List<dynamic>)
          .map((p) => IncomeExpenseDetailType.fromJson(p))
          .toList(),
    );

    IncomeExpenseType.init(
      Map<String, String>.from(result['incomeExpenseType']),
    );

    PaymentType.init(Map<String, String>.from(result['paymentType']));

    if (result['recentSearchListMaxCount'] != null) {
      GeneralConfig.recentSearchListMaxCount =
          result['recentSearchListMaxCount'];
    }

    // Baidu Map ONLY supports setting android AK in android\app\src\main\AndroidManifest.xml by now.
    // We did NOT request iOS AK yet.
    // MapConfig.androidKey = result['baiduMap']['androidKey'];
    // MapConfig.iosKey = result['baiduMap']['iosKey'];
  }
}
